# koroFileHeader at Yungoal acer
# Create: 2022-10-09 14:53:11
# LastEdit: 2022-11-14 16:23:35
"""报表相关模型"""
__author__ = '749B'

import datetime
import json

from django.db import models
from django.db.models import Max
from django.utils import dateparse

from .project import Subscriptions
from .validators import validate_azure_id, part_number

from typing import Dict, Mapping, Union
# Typing.
_Dict = Dict[str, Union[str, int]]
_Mapping = Mapping[str, Union[str, int]]


class Summary(models.Model):
    report_month = models.DateField(verbose_name='账单月', unique=True)
    beginning_balance = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='月初余额')  # 门户：期初余额
    new_purchases = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='本月充值')  # 门户：新购买
    adjustments = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='调整')  # 门户：调整。这个是负值或零
    utilized = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='使用量')  # 门户：预付款使用情况。从余额中扣除的费用
    ending_balance = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='当前余额')  # 门户：期末余额
    service_overage = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='服务超额')  # 如果余额不足，多出的费用记在这里
    charges_billed_separately = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='单独计费的费用')
    total_overage = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='总超额')  # 门户：服务超额
    total_usage = models.DecimalField(max_digits=12, decimal_places=2, verbose_name='总消费金额')  # 使用量(花费的) + 总超额（欠费的）
    report_generation_date = models.DateField(verbose_name='报表生成日期')
    # 防止任务中途失败，记录该月份对应的Detail和PriceSheet表的状态
    detail_generation_date = models.DateField(blank=True, null=True, verbose_name='Detail表生成日期')
    has_price_sheet = models.BooleanField(default=False, verbose_name='是否已经生成了PriceSheet表')

    def __str__(self):
        return self.report_month.strftime('%Y-%m')

    class Meta:
        verbose_name = '余额和费用'
        verbose_name_plural = '月度费用报表'
        
    @staticmethod
    def dict_from_api(m: _Mapping) -> Dict[str, Union[str, int, datetime.date]]:
        assert isinstance(m['Report Generation Date'], str)
        month, day, year = m['Report Generation Date'].split('/')
        report_generation_date = dateparse.parse_date(f'{year}-{month}-{day}')
        data = {
            'beginning_balance': m['Beginning Balance'],
            'new_purchases': m['New Purchases'],
            'adjustments': m['Adjustments'],
            'utilized': m['Utilized ( subtracted from balance)'],
            'ending_balance': m['Ending Balance'],
            'service_overage': m['Service Overage'],
            'charges_billed_separately': m['Charges Billed Separately'],
            'total_overage': m['Total Overage'],
            'total_usage': m['Total Usage (Commitment Utilized + Overage)'],
            'report_generation_date': report_generation_date,
            }
        return data


class Detail(models.Model):
    """没有唯一约束，只能做全量更新。
    如果反复开关机，还会生成多条记录，各个属性都是一样的。
    已消耗资源数量可能会不一样，但这也不能作为依据。
    """
    account_owner_id = models.CharField(max_length=128, verbose_name='账户所有者 Live Id')
    account_name = models.CharField(max_length=128, verbose_name='账户名称')
    service_administrator_id = models.CharField(max_length=128, verbose_name='服务管理员 Live Id')
    date = models.DateField(verbose_name='费用产生日期')
    product = models.CharField(max_length=256, verbose_name='产品')
    meter_id = models.CharField(max_length=128, validators=[validate_azure_id], verbose_name='资源 GUID')
    meter_category = models.CharField(max_length=128, verbose_name='服务')
    meter_sub_category = models.CharField(max_length=128, verbose_name='服务类型')
    meter_region = models.CharField(max_length=128, verbose_name='服务区域')
    meter_name = models.CharField(max_length=128, verbose_name='服务资源')
    consumed_quantity = models.FloatField(verbose_name='已消耗资源数量')
    resource_rate = models.FloatField(verbose_name='资源费率')
    extended_cost = models.FloatField(verbose_name='扩展的成本')
    resource_location = models.CharField(max_length=128, verbose_name='服务子区域')
    consumed_service = models.CharField(max_length=128, verbose_name='服务信息')
    instance_id = models.CharField(max_length=512, verbose_name='组件')
    service_info1 = models.CharField(max_length=128, verbose_name='服务信息 1')
    service_info2 = models.CharField(max_length=128, verbose_name='服务信息 2')
    additional_info = models.JSONField(null=True, verbose_name='附加信息')
    tags = models.JSONField(null=True, verbose_name='标签')
    store_service_identifier = models.CharField(max_length=128)
    department_name = models.CharField(max_length=128)
    cost_center = models.CharField(max_length=128)
    unit_of_measure = models.CharField(max_length=128)
    resource_group = models.CharField(max_length=128)

    subscription = models.ForeignKey(Subscriptions, on_delete=models.SET_NULL, blank=True, null=True)

    def save(self, *args, **kwargs):
        """instance_id转成全小写"""
        self.instance_id = self.instance_id.lower()
        super().save(*args, **kwargs)
        
    class Meta:
        verbose_name = '使用量详细信息'
        verbose_name_plural = '使用量详情报表'

    @staticmethod
    def dict_from_api(m: _Mapping, sub: Subscriptions) -> _Dict:
        assert isinstance(m['Date'], str)
        month, day, year = m['Date'].split('/')
        date = dateparse.parse_date(f'{year}-{month}-{day}')
        assert isinstance(m['InstanceId'], str)
        instance_id_low = m['InstanceId'].lower()
        data = {
            'account_owner_id': m['AccountOwnerId'],
            'account_name': m['AccountName'],
            'service_administrator_id': m['ServiceAdministratorId'],
            'subscription': sub,
            'date': date,
            'product': m['Product'],
            'meter_id': m['MeterId'],
            'meter_category': m['MeterCategory'],
            'meter_sub_category': m['MeterSubCategory'],
            'meter_region': m['MeterRegion'],
            'meter_name': m['MeterName'],
            'consumed_quantity': m['ConsumedQuantity'],
            'resource_rate': m['ResourceRate'],
            'extended_cost': m['ExtendedCost'],
            'resource_location': m['ResourceLocation'],
            'consumed_service': m['ConsumedService'],
            'instance_id': instance_id_low,
            'service_info1': m['ServiceInfo1'],
            'service_info2': m['ServiceInfo2'],
            'additional_info': json.dumps(m['AdditionalInfo']),
            'tags': json.dumps(m['Tags']),
            'store_service_identifier': m['StoreServiceIdentifier'],
            'department_name': m['DepartmentName'],
            'cost_center': m['CostCenter'],
            'unit_of_measure': m['UnitOfMeasure'],
            'resource_group': m['ResourceGroup'],
            }
        return data


class PriceSheet(models.Model):
    report_month = models.DateField(verbose_name='账单月')
    service = models.CharField(max_length=256, verbose_name='服务')  # 可读性描述。匹配时使用part_number(商品编号)
    value_of_measure = models.IntegerField(verbose_name='度量值')
    unit_of_measure = models.CharField(max_length=32, verbose_name='度量单位')
    part_number = models.CharField(max_length=16, validators=[part_number], verbose_name='商品编号')  # 商品编号每月的表格里是唯一的
    unit_price = models.DecimalField(max_digits=12, decimal_places=4, verbose_name='单位价格')  # API返回的精度是4位小数
    currency_code = models.CharField(max_length=16, verbose_name='货币')
    
    class Meta:
        verbose_name = '价目表'
        verbose_name_plural = '价目报表'
        # 联合唯一约束
        constraints = [
            # 同一个服务可能会出现多条，不过商品编号是不一样的。
            # 差别在度量单位上，比如一个是/Day，另一个是/Hour，但是单价换算下来也是一样的。
            # 上面那条不绝对，也有完全一样的，但是part_number不同。甚至没有单位（单位显示NULL），值一样但是单价却不同的。
            models.UniqueConstraint(
                fields=['report_month', 'part_number'],
                name='unique_part_number_monthly',
                ),
            ]

    @staticmethod
    def dict_from_api(m: _Mapping, month_date: datetime.date) -> _Dict:
        assert isinstance(m['UnitOfMeasure'], str)
        value_and_unit = m['UnitOfMeasure'].split(maxsplit=1)
        if len(value_and_unit) == 1:
            value, unit = value_and_unit[0], ''
        else:
            value, unit = value_and_unit
        data = {
            'report_month': month_date,
            'service': m['Service'],
            'value_of_measure': value.replace(',', ''),
            'unit_of_measure': unit,
            'part_number': m['PartNumber'],
            'unit_price': m['UnitPrice'],
            'currency_code': m['CurrencyCode'],
            }
        return data

    @classmethod
    def get_vm_price(cls, date: datetime.date, service: str) -> 'PriceSheet':
        """查询虚拟机的价格信息"""
        report_month = date.replace(day=1)
        return cls.objects.get(report_month=report_month, service=service)

